package main

import (
	"fmt"
	"net"
	"strings"
	"time"
)

type Client struct {
	C    chan string //用户发送数据的管道
	Name string      //用户名
	Addr string      //网络地址
}

var onlineMap map[string]Client //保存在线用户   cliAddr =====> Client

var messaage = make(chan string)

func main() {
	listener, err := net.Listen("tcp", ":8000") //监听
	if err != nil {
		fmt.Println("net.Listen err = ", err)
		return
	}
	defer listener.Close()

	go Manager() //新开一个协程，转发消息，只要有消息来了，遍历map, 给map每个成员都发送此消息

	for { //主协程，循环阻塞等待用户链接
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("listener.Accept err = ", err)
			continue
		}
		go HandleConn(conn) //处理用户链接
	}

}

//新开一个协程，转发消息，只要有消息来了，遍历map, 给map每个成员都发送此消息
func Manager() {
	onlineMap = make(map[string]Client) //给map分配空间
	for {
		msg := <-messaage               //没有消息前，这里会阻塞
		for _, cli := range onlineMap { //遍历map, 给map每个成员都发送此消息
			cli.C <- msg
		}
	}
}

func WriteMsgToClient(cli Client, conn net.Conn) {
	for msg := range cli.C { //给当前客户端发送信息
		conn.Write([]byte(msg + "\n"))
	}
}

func MakeMsg(cli Client, msg string) (buf string) {
	buf = "[" + cli.Addr + "]" + cli.Name + ": " + msg
	return
}

func HandleConn(conn net.Conn) { //处理用户链接
	defer conn.Close()
	cliAddr := conn.RemoteAddr().String()              //获取客户端的网络地址
	cli := Client{make(chan string), cliAddr, cliAddr} //创建一个结构体, 默认，用户名和网络地址一样
	onlineMap[cliAddr] = cli                           //把结构体添加到map

	go WriteMsgToClient(cli, conn) //新开一个协程，专门给当前客户端发送信息

	messaage <- MakeMsg(cli, "login") //广播某个在线

	cli.C <- MakeMsg(cli, "I am here") //提示，我是谁

	isQuit := make(chan bool)  //对方是否主动退出
	hasData := make(chan bool) //对方是否有数据发送

	go func() { //新建一个协程，接收用户发送过来的数据
		buf := make([]byte, 2048)
		for {
			n, err := conn.Read(buf)
			if n == 0 { //对方断开，或者，出问题
				isQuit <- true
				fmt.Println("conn.Read err = ", err)
				return
			}
			msg := string(buf[:n-1])           //通过windows nc测试，多一个换行
			if len(msg) == 3 && msg == "who" { //遍历map，给当前用户发送所有成员
				conn.Write([]byte("user list:\n"))
				for _, tmp := range onlineMap {
					msg = tmp.Addr + ":" + tmp.Name + "\n"
					conn.Write([]byte(msg))
				}
			} else if len(msg) >= 8 && msg[:6] == "rename" { // rename|mike
				name := strings.Split(msg, "|")[1]
				cli.Name = name
				onlineMap[cliAddr] = cli
				conn.Write([]byte("rename ok\n"))
			} else { //转发此内容
				messaage <- MakeMsg(cli, msg)
			}
			hasData <- true //代表有数据,用户在线
		}
	}()
	for { //通过select检测channel的流动
		select {
		case <-isQuit:
			delete(onlineMap, cliAddr)               //当前用户从map移除
			messaage <- MakeMsg(cli, " logined out") //广播谁下线了
			return
		case <-hasData:
		case <-time.After(45 * time.Second): //60s后
			delete(onlineMap, cliAddr)                         //当前用户从map移除
			messaage <- MakeMsg(cli, " time out && leave out") //广播谁下线了
			return
		}
	}
}
